package ndfs.mcndfs_1_naive;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import graph.State;
import graph.Graph;
import ndfs.NDFS;
import ndfs.Result;
import ndfs.CycleFound;
import ndfs.NoCycleFound;

public class NNDFS implements NDFS {

	private int threadId = 0;
	private Graph graph;
	private Colors colors;
	private Map<State, Boolean> pink;
	private Map<State, Boolean> red;
	private Map<State, Integer> counter;

	public NNDFS(Graph graph, Map<State, Color> colorStore,
			Map<State, Boolean> red, Map<State, Integer> counter) {
		this.graph = graph;
		this.colors = new Colors(colorStore);
		this.pink = new HashMap<State, Boolean>();
		this.red = red;
		this.counter = counter;

	}

	public void init() {
	}

	public void setThreadId(int thread) {
		threadId = thread;
	}

	private Queue<State> getSortedQueue(State s) {
		Queue<State> result = new LinkedList<State>();

		List<State> allStates = graph.post(s);
		for (int i = 0; i < allStates.size(); i++) {
			// TODO Slim maniertje om threads the verdelen.
			result.add(allStates.get((threadId + i) % allStates.size()));
		}

		return result;
	}

	private boolean isRed(State s) {
		Boolean b = null;
		synchronized (red) {
			b = red.get(s);
		}
		return !(b == null || b == false);
	}

	private void dfsRed(State s) throws Result {
		pink.put(s, true);
		Queue<State> sortedQueue = getSortedQueue(s);
		int states = sortedQueue.size();
		for (int i = 0; i < states; i++) {
			State t = sortedQueue.remove();
			if (colors.hasColor(t, Color.CYAN)) {
				throw new CycleFound();
			}
			Boolean b = pink.get(t);
			if ((b == null || b == false) && !isRed(t)) {
				dfsRed(t);
			}

			// else if (colors.hasColor(t, Color.BLUE)) {
			// colors.color(t, Color.RED);
			// dfsRed(t);
			// }
		}

		if (s.isAccepting()) {
			decrementCounter(s);
			awaitCounterIsZero(s);
		}

		synchronized (red) {
			red.put(s, true);
		}
		pink.put(s, false);

	}

	private void awaitCounterIsZero(State s) {
		boolean running = true;
		while (running) {
			running = !checkZero(s);
		}
	}

	private synchronized boolean checkZero(State s) {
		boolean isZero = false;
		synchronized (counter) {
			isZero = counter.get(s) == 0;
		}
		return isZero;
	}

	private void decrementCounter(State s) {
		synchronized (counter) {
			
			Integer old = counter.get(s);
			// Just a failsafe.
			if (old != null) {
				counter.put(s, old - 1);
			} else {
				// ERROR ERROR ERROR CANNOT COMPUTE!!!
				System.exit(2);
			}
		}
	}

	private void incrementCounter(State s) {

		synchronized (counter) {
			
			Integer old = counter.get(s);
			if (old != null) {
				counter.put(s, old + 1);
			} else {
				counter.put(s, 1);
			}
		}
	}

	private void dfsBlue(State s) throws Result {
		colors.color(s, Color.CYAN);
		Queue<State> sortedQueue = getSortedQueue(s);
		int states = sortedQueue.size();
		for (int i = 0; i < states; i++) {
			State t = sortedQueue.remove();
			if (colors.hasColor(t, Color.WHITE) && !isRed(t)) {
				dfsBlue(t);
			}
		}
		if (s.isAccepting()) {
			incrementCounter(s);
			dfsRed(s);
		}
		colors.color(s, Color.BLUE);

	}

	private void nndfs(State s) throws Result {
		dfsBlue(s);
		throw new NoCycleFound();
	}

	public void ndfs() throws Result {
		nndfs(graph.getInitialState());
	}
}
